﻿#pragma  warning(disable:4996)
#include <stdio.h>
#include <graphics.h>        //调用EasyX的图形库
#include <windows.h>
#include <conio.h>
#include <time.h>
#include <stdlib.h>
#include <wchar.h>
#pragma comment(lib, "Winmm.lib")//调用有关多媒体的动态连接库

#define GAMEWIDTH 640       //游戏窗口的宽
#define GAMEHIGH  480		//游戏窗口的高
#define N		  20		//蛇每节的大小20x20
#define SNAKELEN  1000		//蛇的最大长度1000
#define SPEED	  180	    //定义初始的移动速度
#define PATHFILE  "rank.txt"//分数存储的文件

//自定义函数
//——————————————————————————————

void menu();                //进入游戏的主菜单界面
void about();               //关于游戏界面
void initgame();            //初始化游戏界面
void OverGame();			//游戏失败界面
void gameegg();				//游戏彩蛋界面

void snakepic();		    //加载蛇头蛇身的图片
void showsnake();			//生成蛇
void showfood();			//加载食物图片
void snakepicmove();		//控制蛇移动
void setfoodcrd();			//食物的生成坐标
void EatFood();				//吃到食物
void GameEnd();				//游戏退出和触发彩蛋的条件
void SnakeDirection();		//蛇头的方向
void addscore();			//显示分数
int rank();					//储存排行榜
int readrank();				//输出排行榜
void speedup();				//游戏加速

//自定义结构体和全局变量
//——————————————————————————————

MOUSEMSG m;					//定义鼠标，引用Exasy库
int s;						//定义游戏分数

// 方便以后在程序中改变速度
int speed = SPEED;
//定义使用的外部图片
IMAGE foods, snakeup, snakedown, snakeleft, snakerigth, snakebody, background, gameoverbk, reback, scorebk, gameeggbk;

typedef struct inf			//定义一个结构体存放分数
{
	int score;
}info;

enum DIR					//运动方向的结构体
{
	up = 72,				//上、下、左、右、空格键ASCII码
	down = 80,
	left = 75,
	right = 77,
	space = 32
};

typedef struct COOR			//表示坐标的结构体
{
	int x;					//横坐标
	int y;					//纵坐标
};

struct SNAKE				//定义结构体，来存储蛇的相关信息
{
	COOR crd[SNAKELEN];		//保存蛇的每个结数的坐标，最多有1000节
	int node;				//保存蛇当前节数
	DIR dir;				//当前蛇行进的方向
}snake;

struct FOOD					//食物的结构体
{
	COOR crd;				//食物坐标
	int flag;				//表示有没有食物 1代表没有食物，没有食物需要生成食物
}food;


//=================================================================
//主函数
int main()
{
	initgraph(GAMEWIDTH, GAMEHIGH);			//创建一个680X480窗口
	menu();									//主菜单函数
	return 0;								//程序正常结束
}

//主菜单界面模块
void menu()
{
	loadimage(&background, _T("pic\\bk.jpg"), 640, 480);	//从文件中读取图像
	putimage(0, 0, &background);							//重载用于在当前设备上绘制指定图像
	setbkmode(TRANSPARENT);									//字体背景透明
	while (true)
	{
		while (MouseHit())									//检测当前是否有鼠标消息
		{
			m = GetMouseMsg();								//函数用于获取一个鼠标消息
			if (m.uMsg == WM_LBUTTONDOWN)					//左键按下消息
			{
				if (m.x >= 60 && m.x <= 280)				//鼠标点击的区域横坐标为60-280
				{
					if (m.y >= 160 && m.y <= 215)			//鼠标点击的区域纵坐标为160-215
					{
						mciSendString(_T("open pic\\Sushi.mp3 alias mysong"), NULL, 0, NULL);		//从指定目录调用音乐
						mciSendString(_T("play MySong repeat"), NULL, 0, NULL);						//播放音乐
						initgame();																	//进入游戏游戏界面
					}
					if (m.y >= 260 && m.y <= 315)
					{
						//分数排行榜
						readrank();
					}
					if (m.y >= 360 && m.y <= 415)
					{
						//关于游戏
						about();
					}
				}
			}
		}
	}

}

//游戏说明界面
void about()
{
	cleardevice();										//清除屏幕内容
	loadimage(&reback, _T("pic\\aboutbk.jpg"), 640, 480);//背景图片
	putimage(0, 0, &reback);
	while (true)										//鼠标点击，返回主菜单界面

	{
		if (MouseHit())
		{
			m = GetMouseMsg();
			if (m.uMsg == WM_LBUTTONDOWN)
			{
				if (m.x >= 540 && m.x <= 660 && m.y >= 417 && m.y <= 460)
				{
					menu();								//主菜单函数
				}
			}
		}
	}
}

//载入蛇身，蛇头的图片
void snakepic()
{
	setbkcolor(WHITE);				//背景窗口设置为白色
	loadimage(&snakeup, _T("pic\\蛇头上.jpg"), N, N);
	loadimage(&snakedown, _T("pic\\蛇头下.jpg"), N, N);
	loadimage(&snakeleft, _T("pic\\蛇头左.jpg"), N, N);
	loadimage(&snakerigth, _T("pic\\蛇头右.jpg"), N, N);
	loadimage(&snakebody, _T("pic\\贪吃蛇身体.jpg"), N, N);
	snake.crd[0].x = 0;				//蛇出现的横坐标
	snake.crd[0].y = 0;				//蛇出现的纵坐标
	snake.node = 1;					//蛇出生的节数
	snake.dir = down;				//蛇头运动的初始方向
	food.flag = 0;					//没有食物
	cleardevice();					//清除屏幕内容
}

//初始化游戏的界面，运行游戏
void initgame()
{
	srand(time(NULL));		//设置一个随机种子，每次运行都能保证随机种子不同。
	snakepic();				//载入蛇身，蛇头的图片
	s = -10;				//循环开始时，将分数设置为负数
	while (1)
	{
		while (!_kbhit())
		{
			if (!food.flag)	//食物不存在，就生成食物
			{
				setfoodcrd();
				s += 10;	//每生产一次食物加10分
			}
			cleardevice();
			showfood();		//食物图片
			addscore();		//左上角显示分数
			snakepicmove();	//
			showsnake();	//
			Sleep(speed);	//移动速度
			EatFood();		//吃到食物
			GameEnd();	//游戏结束条件
		}
		SnakeDirection();
	}
}

//随机生成食物的坐标
void setfoodcrd()
{
	food.crd.x = rand() % (640 / N) * N;
	food.crd.y = rand() % (480 / N) * N;
	food.flag = 1;
}

//放置食物的图片
void showfood()
{
	loadimage(&foods, _T("pic\\食物.jpg"), N, N);
	putimage(food.crd.x, food.crd.y, &foods);
}

//放置蛇头以及蛇身的图片
void showsnake()
{
	//蛇头
	switch (snake.dir)//获取运动方向，改变蛇头图片
	{
	case up:
		putimage(snake.crd[0].x, snake.crd[0].y, &snakeup);
		break;
	case down:
		putimage(snake.crd[0].x, snake.crd[0].y, &snakedown);
		break;
	case left:
		putimage(snake.crd[0].x, snake.crd[0].y, &snakeleft);
		break;
	case right:
		putimage(snake.crd[0].x, snake.crd[0].y, &snakerigth);
		break;
	default:
		break;
	}
	//蛇身
	//通过一个从（node-1）到1的循环，生成蛇身，进行贴图
	for (int i = snake.node - 1; i > 0; i--)
	{
		putimage(snake.crd[i].x, snake.crd[i].y, &snakebody);
	}
}

//蛇体贴图坐标的移动
void snakepicmove()
{
	//倒数第一节蛇身的贴图坐标等于倒数第二节
	for (int i = snake.node - 1; i > 0; i--)
	{
		snake.crd[i].x = snake.crd[i - 1].x;
		snake.crd[i].y = snake.crd[i - 1].y;
	}
	switch (snake.dir)
	{
	case up:
		snake.crd[0].y -= N;
		break;
	case down:
		snake.crd[0].y += N;
		break;
	case left:
		snake.crd[0].x -= N;
		break;
	case right:
		snake.crd[0].x += N;
		break;
	default:
		break;
	}
}

//键盘操作控制蛇移动方向
void SnakeDirection()
{
	switch (_getch())
	{
	case 72:
		if (snake.dir != down)
			snake.dir = up;
		break;
	case 80:
		if (snake.dir != up)
			snake.dir = down;
		break;
	case 75:
		if (snake.dir != right)
			snake.dir = left;
		break;
	case 77:
		if (snake.dir != left)
			snake.dir = right;
		break;
	case 32://空格暂停
		system("pause");
	default:
		break;
	}
}

//吃到食物：食物的坐标和蛇头坐标的重合
void EatFood()
{
	if (snake.crd[0].x == food.crd.x && snake.crd[0].y == food.crd.y)//食物的坐标和蛇头坐标(snake.crd.[0])的重合
	{
		snake.node++;	//节数增加
		food.flag = 0;	//生成食物的标志变为0
		speedup();		//每次吃到食物后，加速
	}
}

//判断游戏退出
void GameEnd()
{
	//撞墙
	if (snake.crd[0].y < -1 || snake.crd[0].y>471 || snake.crd[0].x < -1 || snake.crd[0].x>631)
	{
		//退出
		OverGame();
	}
	//吃到自己
	for (int i = snake.node - 1; i > 0; i--)
	{
		if (snake.crd[0].x == snake.crd[i].x && snake.crd[0].y == snake.crd[i].y)
		{
			//退出
			OverGame();
		}
	}
	//分数到达520
	if (s == 520)
	{
		//彩蛋界面
		gameegg();
	}
}

//游戏结束画面及相关操作
void OverGame()
{

	mciSendString(_T("close MySong"), NULL, 0, NULL);	//游戏结束，关闭背景音乐
	loadimage(&gameoverbk, _T("pic\\gameoverbk.jpg"), 640, 480);
	putimage(0, 0, &gameoverbk);
	setbkmode(TRANSPARENT);								//字体背景透明
	settextcolor(BLACK);								//黑色字体
	settextstyle(31, 31, _T("字魂27号-布丁体"));			//字体大小，样式

	TCHAR myscore[20];									//定义一个数组用来显示分数
	swprintf_s(myscore, _T("你的分数:%d"), s);			//输出数组
	outtextxy(226, 246, myscore);						//文本输出的位置

	//鼠标操作
	while (true)
	{
		while (MouseHit())
		{
			m = GetMouseMsg();

			if (m.uMsg == WM_LBUTTONDOWN)
			{
				if (m.x >= 220 && m.x <= 420)
				{
					if (m.y >= 250 && m.y <= 295)
					{
						rank();
					}
					if (m.y >= 320 && m.y <= 365)
					{
						rank();
						menu();
					}
					if (m.y >= 390 && m.y <= 435)
					{
						rank();
						exit(0);
					}
				}
			}
		}
	}
}

//游戏分数的显示
void addscore()
{
	TCHAR myscore[20];
	swprintf_s(myscore, _T("score:%d"), s);// 输出数值，先将数字格式化输出为字符串
	settextcolor(BLACK);
	outtextxy(0, 0, myscore);//输出分数的文本内容
}


//创建一个文件储存分数
int rank()
{
	info tmp;
	FILE* fp;
	tmp.score = s;
	fp = fopen(PATHFILE, "a");//追加 向文本文件尾添加数据
	if (fp == NULL)
	{
		return -1;
	}
	fwrite(&tmp, sizeof(struct inf), 1, fp);
	fclose(fp);
	return 0;
}

//分数排行榜
int readrank()

{	//载入分数界面的背景图片------------------------------
	loadimage(&scorebk, _T("pic\\scorebk.jpg"), 640, 480);
	putimage(0, 0, &scorebk);

	//读取分数进行排序------------------------------------
	info tmp[N];
	info temp;                 //空的结构体用于交换
	int n = 0, j = 0, i = 0, k = 0;
	FILE* fp;
	fp = fopen(PATHFILE, "a+");
	if (fp == NULL)
	{
		printf("cannot open the files\n");
	}
	while (!feof(fp))			//数组tmp读取文件,未到达文件尾
	{
		fread(&tmp[i], sizeof(struct inf), 1, fp);
		i++;					//每读取一条数据，i+1
	}

	n = i - 1;					//记录排行榜人数
	for (i = 0; i < n; i++)		//冒泡排序对分数排序
		for (j = i + 1; j < n; j++)
			if (tmp[i].score < tmp[j].score)
			{
				temp = tmp[i];
				tmp[i] = tmp[j];
				tmp[j] = temp;
			}

	//显示排序后的分数--------------------------------------
	TCHAR myscore[20];
	if (i >= 10)						//当记录的分数数据超过10条
	{
		for (i = 0; i < 10; i++)	//只显示前10个分数
		{
			swprintf_s(myscore, _T("%d         %d"), i + 1, tmp[i].score);
			settextcolor(BLACK);
			settextstyle(25, 25, _T("字魂75号-柳叶竹眉体"));
			outtextxy(185, 120 + i * 20, myscore);//输出分数的文本内容
		}
		fclose(fp);
	}
	if (i < 10)						//当记录的分数数据不超过10条	
	{
		for (k = 0; k < i; k++)			//只显示前i条分数
		{
			swprintf_s(myscore, _T("%d         %d"), k + 1, tmp[k].score);
			settextcolor(BLACK);
			settextstyle(25, 25, _T("字魂75号-柳叶竹眉体"));
			outtextxy(185, 120 + k * 20, myscore);//输出分数的文本内容
		}
		fclose(fp);//关闭文件
	}

	//鼠标操作返回主菜单--------------------------------------
	while (true)
	{
		while (MouseHit())
		{
			m = GetMouseMsg();
			if (m.uMsg == WM_LBUTTONDOWN)
			{
				if (m.x >= 540 && m.x <= 660 && m.y >= 417 && m.y <= 460)//返回主菜单界面
				{
					menu();
				}
				if (m.x >= 460 && m.x <= 500 && m.y >= 417 && m.y <= 460)
				{
					remove(PATHFILE);
					//清空记录
				}
			}
		}
	}
}
//游戏彩蛋
void gameegg()
{
	mciSendString(_T("close MySong"), NULL, 0, NULL);
	mciSendString(_T("open pic\\InsideStory.mp3 alias mysong2"), NULL, 0, NULL);
	mciSendString(_T("play MySong2 repeat"), NULL, 0, NULL);
	loadimage(&gameeggbk, _T("pic\\gameeggbk.jpg"), 640, 480);
	putimage(0, 0, &gameeggbk);
	//返回菜单
	while (true)
	{
		while (MouseHit())
		{
			m = GetMouseMsg();
			if (m.uMsg == WM_LBUTTONDOWN)
			{
				if (m.x >= 540 && m.x <= 660 && m.y >= 417 && m.y <= 460)//返回主菜单界面
				{
					mciSendString(_T("close MySong2"), NULL, 0, NULL);	//游戏结束，关闭背景音乐
					rank();
					menu();
				}
			}
		}
	}
}

//游戏加速
void speedup()
{
	if (speed >= 80)
	{
		speed = speed - 10;
	}
}

